﻿// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

// ReSharper disable CheckNamespace
// ReSharper disable CommentTypo
// ReSharper disable IdentifierTypo
// ReSharper disable InconsistentNaming
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable ReturnTypeCanBeNotNullable
// ReSharper disable StringLiteralTypo
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedParameter.Local

#pragma warning disable CA1816

/* SyncConnection.cs -- синхронное подключение к серверу ИРБИС64
 * Ars Magna project, http://arsmagna.ru
 */

#region Using directives

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

using AM;
using AM.Collections;
using AM.IO;

using ManagedIrbis.Gbl;
using ManagedIrbis.Infrastructure;
using ManagedIrbis.Infrastructure.Sockets;
using ManagedIrbis.Performance;
using ManagedIrbis.Providers;
using ManagedIrbis.Records;

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;

#endregion

#nullable enable

namespace ManagedIrbis;

/// <summary>
/// Синхронное подключение к серверу ИРБИС64.
/// </summary>
public class SyncConnection
    : ConnectionBase,
    ISyncConnection
{
    #region Properties

    /// <summary>
    /// Сокет.
    /// </summary>
    public ISyncClientSocket Socket { get; }

    #endregion

    #region Construction

    /// <summary>
    /// Конструктор по умолчанию.
    /// </summary>
    public SyncConnection()
        : this (null, null)
    {
        // пустое тело конструктора
    }

    /// <summary>
    /// Конструктор.
    /// </summary>
    public SyncConnection
        (
            ISyncClientSocket? socket,
            IServiceProvider? serviceProvider
        )
        : base (serviceProvider ?? Magna.Host.Services)
    {
        Socket = socket ?? new SyncTcp4Socket();
        Socket.Connection = this;

        _performanceCollector = (IPerformanceCollector?) serviceProvider?.GetService (typeof (IPerformanceCollector));
        _logger = (ILogger?)(serviceProvider?.GetService (typeof (ILogger<SyncConnection>))
                             ?? NullLogger.Instance);
    }

    #endregion

    #region Public methods

    /// <summary>
    /// Отправка запроса на сервер по упрощённой схеме.
    /// </summary>
    /// <param name="command">Код команды.</param>
    /// <param name="args">Опциональные параметры команды
    /// (в кодировке ANSI).</param>
    /// <returns>Ответ сервера.</returns>
    public Response? ExecuteSync
        (
            string command,
            params object[] args
        )
    {
        if (!CheckProviderState())
        {
            return null;
        }

        using var query = new SyncQuery (this, command);
        foreach (var arg in args)
        {
            query.AddAnsi (arg.ToString());
        }

        var result = ExecuteSync (query);

        return result;
    }

    /// <summary>
    /// Отправка запроса на сервер по упрощённой схеме.
    /// </summary>
    /// <param name="command">Код команды.</param>
    /// <returns>Ответ сервера.</returns>
    public Response? ExecuteSync
        (
            string command
        )
    {
        if (!CheckProviderState())
        {
            return null;
        }

        using var query = new SyncQuery (this, command);
        var result = ExecuteSync (query);

        return result;
    }

    /// <summary>
    /// Отправка запроса на сервер по упрощённой схеме.
    /// </summary>
    /// <param name="command">Код команды.</param>
    /// <param name="arg1">Первый и единственный параметр команды.</param>
    /// <returns>Ответ сервера.</returns>
    public Response? ExecuteSync
        (
            string command,
            object arg1
        )
    {
        if (!CheckProviderState())
        {
            return null;
        }

        using var query = new SyncQuery (this, command);
        query.AddAnsi (arg1.ToString());

        var result = ExecuteSync (query);

        return result;
    }

    #endregion

    #region ISyncConnection members

    /// <summary>
    /// Отправка клиентского запроса на сервер
    /// и получение ответа от него.
    /// </summary>
    /// <param name="query">Клиентский запрос.</param>
    /// <returns>Ответ от сервера.</returns>
    public Response? ExecuteSync
        (
            SyncQuery query
        )
    {
        PerfRecord? perfRecord = null;
        Stopwatch? stopwatch = null;
        if (_performanceCollector is not null)
        {
            stopwatch = new Stopwatch();
            stopwatch.Start();
            perfRecord = new PerfRecord
            {
                Moment = DateTime.Now,
                Host = Host,
                Code = "none", // TODO: нужно где-то прикопать код операции
                OutgoingSize = query.GetLength()
            };
        }

        SetBusy (true);

        try
        {
            if (_cancellation.IsCancellationRequested)
            {
                _cancellation = new CancellationTokenSource();
            }

            Response? result;
            try
            {
                //if (_debug)
                //{
                //    query.Debug (Console.Out);
                //}

                result = Socket.TransactSync (query);
            }
            catch (Exception exception)
            {
                _logger?.LogError (exception, nameof (ExecuteSync));
                Debug.WriteLine (exception.Message);
                if (perfRecord is not null)
                {
                    perfRecord.ElapsedTime = (int)stopwatch!.ElapsedMilliseconds;
                    perfRecord.ErrorMessage = exception.Message;
                    _performanceCollector!.Collect (perfRecord);
                }

                return null;
            }

            if (result is null)
            {
                if (perfRecord is not null)
                {
                    perfRecord.ElapsedTime = (int) stopwatch!.ElapsedMilliseconds;
                    perfRecord.ErrorMessage = "No response";
                    _performanceCollector!.Collect (perfRecord);
                }

                return null;
            }

            //if (_debug)
            //{
            //    result.Debug (Console.Out);
            //}

            result.Parse();
            if (perfRecord is not null)
            {
                perfRecord.ElapsedTime = (int)stopwatch!.ElapsedMilliseconds;
                perfRecord.IncomingSize = result.AnswerSize;
                _performanceCollector!.Collect (perfRecord);
            }

            Interlocked.Increment (ref _queryId);

            return result;
        }
        finally
        {
            SetBusy (false);
        }
    }

    /// <summary>
    /// Получение статистики по базе данных.
    /// </summary>
    public string? GetDatabaseStat
        (
            StatDefinition definition
        )
    {
        if (!CheckProviderState())
        {
            return null;
        }

        using var query = new SyncQuery (this, CommandCode.DatabaseStat);
        definition.Encode (this, query);

        using var response = ExecuteSync (query);
        if (!response.IsGood())
        {
            return null;
        }

        var result = "{\\rtf1 "
                     + response.ReadRemainingUtfText()
                     + "}";

        return result;
    }

    /// <summary>
    /// Переподключение к серверу.
    /// </summary>
    public bool Reconnect()
    {
        if (IsConnected)
        {
            Disconnect();
        }

        IniFile?.Dispose();
        IniFile = null;

        return Connect();
    }

    /// <summary>
    /// Остановка сервера (расширенная команда).
    /// </summary>
    public bool StopServer()
    {
        using var response = ExecuteSync ("STOP");
        var result = response is not null;
        IsConnected = false;

        return result;
    }

    /// <summary>
    /// Разблокирование указанной записи (альтернативный вариант).
    /// </summary>
    public bool UnlockRecordAlt (int mfn) =>
        ExecuteSync ("E", EnsureDatabase(), mfn).IsGood();

    #endregion

    #region ISyncProvider members

    /// <inheritdoc cref="ISyncProvider.ActualizeRecord"/>
    public bool ActualizeRecord (ActualizeRecordParameters parameters) =>
        ExecuteSync
            (
                CommandCode.ActualizeRecord,
                EnsureDatabase (parameters.Database),
                parameters.Mfn
            ).IsGood();

    /// <inheritdoc cref="ISyncProvider.Connect"/>
    public bool Connect()
    {
        if (IsConnected)
        {
            return true;
        }

        AGAIN:
        LastError = 0;
        QueryId = 1;
        ClientId = new Random().Next (100000, 999999);

        // нельзя использовать using response из-за goto
        Response? response;
        using (var query = new SyncQuery (this, CommandCode.RegisterClient))
        {
            query.AddAnsi (Username);
            query.AddAnsi (Password);

            response = ExecuteSync (query);
            if (response is null)
            {
                LastError = -100_500;
                return false;
            }
        }

        if (response.GetReturnCode() == -3337)
        {
            response.Dispose();
            goto AGAIN;
        }

        if (response.ReturnCode < 0)
        {
            LastError = response.ReturnCode;
            response.Dispose();
            return false;
        }

        try
        {
            ServerVersion = response.ServerVersion;
            Interval = response.ReadInteger();

            IniFile = new IniFile();
            var remainingText = response.RemainingText (IrbisEncoding.Ansi);
            var reader = new StringReader (remainingText);
            IniFile.Read (reader);
            IsConnected = true;
        }
        finally
        {
            response.Dispose();
        }

        return true;
    }

    /// <inheritdoc cref="ISyncProvider.CreateDatabase"/>
    public bool CreateDatabase
        (
            CreateDatabaseParameters parameters
        )
    {
        if (!CheckProviderState())
        {
            return false;
        }

        using var query = new SyncQuery (this, CommandCode.CreateDatabase);
        query.AddAnsi (EnsureDatabase (parameters.Database));
        query.AddAnsi (parameters.Description);
        query.Add (parameters.ReaderAccess);
        using var response = ExecuteSync (query);

        return response.IsGood();
    }

    /// <inheritdoc cref="ISyncProvider.CreateDictionary"/>
    public bool CreateDictionary (string? databaseName = default) =>
        ExecuteSync (CommandCode.CreateDictionary,
            EnsureDatabase (databaseName)).IsGood();

    /// <inheritdoc cref="ISyncProvider.DeleteDatabase"/>
    public bool DeleteDatabase (string? databaseName = default) =>
        ExecuteSync (CommandCode.DeleteDatabase,
            EnsureDatabase (databaseName)).IsGood();

    /// <inheritdoc cref="ISyncProvider.Disconnect"/>
    public bool Disconnect()
    {
        if (IsConnected)
        {
            try
            {
                OnDisposing();

                using var _ = ExecuteSync (CommandCode.UnregisterClient);
            }
            catch (Exception exception)
            {
                _logger?.LogError
                    (
                        exception,
                        nameof (SyncConnection) + "::" + nameof (Disconnect)
                    );
            }

            IsConnected = false;
        }

        return true;
    }

    /// <inheritdoc cref="ISyncProvider.FileExist"/>
    public bool FileExist (FileSpecification specification) =>
        !string.IsNullOrEmpty (ReadTextFile (specification));

    /// <inheritdoc cref="ISyncProvider.FormatRecords"/>
    public bool FormatRecords
        (
            FormatRecordParameters parameters
        )
    {
        if (!CheckProviderState())
        {
            return false;
        }

        // TODO: обнаруживать Records

        if (parameters.Mfns.IsNullOrEmpty())
        {
            parameters.Result = this.FormatRecord (parameters.Format!, parameters.Mfn)!;
            return true;
        }

        using var query = new SyncQuery (this, CommandCode.FormatRecord);
        query.AddAnsi (EnsureDatabase (parameters.Database));
        query.AddFormat (parameters.Format);
        query.Add (parameters.Mfns.Length);
        foreach (var mfn in parameters.Mfns)
        {
            query.Add (mfn);
        }

        using var response = ExecuteSync (query);
        if (!response.IsGood (false))
        {
            return false;
        }

        var lines = response.ReadRemainingUtfLines();
        var result = new List<string> (lines.Length);
        if (parameters.Mfns.Length == 1)
        {
            result.Add (lines[0]);
        }
        else
        {
            foreach (var line in lines)
            {
                if (string.IsNullOrEmpty (line))
                {
                    continue;
                }

                var parts = line.Split ('#', 2);
                result.Add
                    (
                        parts.Length > 1
                            ? parts[1]
                            : line
                    );
            }
        }

        parameters.Result = result.ToArray();

        return true;
    }

    /// <inheritdoc cref="ISyncProvider.FullTextSearch"/>
    public FullTextResult? FullTextSearch
        (
            SearchParameters searchParameters,
            TextParameters textParameters
        )
    {
        if (!CheckProviderState())
        {
            return null;
        }

        using var query = new SyncQuery (this, CommandCode.NewFulltextSearch);
        searchParameters.Encode (this, query);
        textParameters.Encode (this, query);
        using var response = ExecuteSync (query);
        if (!response.IsGood (false))
        {
            return null;
        }

        var result = new FullTextResult();
        result.Decode (response);

        return result;
    }

    /// <inheritdoc cref="IAsyncProvider.GetDatabaseInfoAsync"/>
    public DatabaseInfo? GetDatabaseInfo (string? databaseName = default) =>
        ExecuteSync (CommandCode.RecordList, EnsureDatabase (databaseName))
            .Transform
                (
                    resp => DatabaseInfo.Parse
                        (
                            EnsureDatabase (databaseName),
                            resp
                        )
                );

    /// <inheritdoc cref="ISyncProvider.GetMaxMfn"/>
    public int GetMaxMfn
        (
            string? databaseName = default
        )
    {
        using var response = ExecuteSync (CommandCode.GetMaxMfn, EnsureDatabase (databaseName));

        response.IsGood();

        return response?.ReturnCode - 1 ?? LastError;
    }

    /// <inheritdoc cref="ISyncProvider.GetServerStat"/>
    public ServerStat? GetServerStat() =>
        ExecuteSync (CommandCode.GetServerStat).Transform (ServerStat.Parse);

    /// <inheritdoc cref="ISyncProvider.GetServerVersion"/>
    public ServerVersion? GetServerVersion() =>
        ExecuteSync (CommandCode.ServerInfo).Transform (ManagedIrbis.ServerVersion.Parse);

    /// <inheritdoc cref="ISyncProvider.GlobalCorrection"/>
    public GblResult? GlobalCorrection
        (
            GblSettings settings
        )
    {
        if (!CheckProviderState())
        {
            return null;
        }

        var database = EnsureDatabase (settings.Database);
        using var query = new SyncQuery (this, CommandCode.GlobalCorrection);
        query.AddAnsi (database);
        settings.Encode (query);

        using var response = ExecuteSync (query);
        if (!response.IsGood())
        {
            return null;
        }

        var result = new GblResult();
        result.Parse (response);

        return result;
    }

    /// <inheritdoc cref="ISyncProvider.ListFiles"/>
    public string[]? ListFiles
        (
            params FileSpecification[] specifications
        )
    {
        if (!CheckProviderState())
        {
            return null;
        }

        if (specifications.Length == 0)
        {
            return Array.Empty<string>();
        }

        using var query = new SyncQuery (this, CommandCode.ListFiles);
        foreach (var specification in specifications)
        {
            query.AddAnsi (specification.ToString());
        }

        using var response = ExecuteSync (query);

        return SyncConnectionUtility.ListFiles (response);
    }

    /// <inheritdoc cref="ISyncProvider.ListProcesses"/>
    public ProcessInfo[]? ListProcesses() =>
        ExecuteSync (CommandCode.GetProcessList).Transform (ProcessInfo.Parse);

    /// <inheritdoc cref="ISyncProvider.ListUsers"/>
    public UserInfo[]? ListUsers() =>
        ExecuteSync (CommandCode.GetUserList).Transform (UserInfo.Parse);

    /// <inheritdoc cref="ISyncProvider.NoOperation"/>
    public bool NoOperation() => ExecuteSync (CommandCode.Nop).IsGood();

    /// <inheritdoc cref="ISyncProvider.PrintTable"/>
    public string? PrintTable (TableDefinition definition)
    {
        using var query = new SyncQuery (this, CommandCode.Print);
        query.AddAnsi (EnsureDatabase (definition.DatabaseName));
        definition.Encode (query);

        using var response = ExecuteSync (query);

        return response?.ReadRemainingUtfText();
    } // method PrintTable

    /// <inheritdoc cref="ISyncProvider.ReadBinaryFile"/>
    public byte[]? ReadBinaryFile
        (
            FileSpecification specification
        )
    {
        specification.BinaryFile = true;
        using var response = ExecuteSync (CommandCode.ReadDocument, specification.ToString());
        if (response is null || !response.FindPreamble())
        {
            return null;
        }

        return response.RemainingBytes();
    }

    /// <inheritdoc cref="ISyncProvider.ReadPostings"/>
    public TermPosting[]? ReadPostings
        (
            PostingParameters parameters
        )
    {
        using var query = new SyncQuery (this, CommandCode.ReadPostings);
        parameters.Encode (this, query);

        using var response = ExecuteSync (query);
        if (!response.IsGood (false, ConnectionUtility.GoodCodesForReadTerms))
        {
            return null;
        }

        return TermPosting.Parse (response);
    }

    /// <inheritdoc cref="ISyncProvider.ReadRecord{T}"/>
    public T? ReadRecord<T>
        (
            ReadRecordParameters parameters
        )
        where T : class, IRecord, new()
    {
        T? result;

        try
        {
            var database = EnsureDatabase (parameters.Database);
            using var query = new SyncQuery (this, CommandCode.ReadRecord);
            query.AddAnsi (database);
            query.Add (parameters.Mfn);
            if (parameters.Version != 0)
            {
                query.Add (parameters.Version);
            }
            else
            {
                query.Add (parameters.Lock);
            }

            query.AddFormat (parameters.Format);

            using var response = ExecuteSync (query);
            if (!response.IsGood (false, ConnectionUtility.GoodCodesForReadRecord))
            {
                return null;
            }

            // TODO: забирать результат расформатирования

            result = new T
            {
                Database = Database
            };

            switch ((ReturnCode)response.ReturnCode)
            {
                case ReturnCode.PreviousVersionNotExist:
                    result.Status |= RecordStatus.Absent;
                    break;

                case ReturnCode.PhysicallyDeleted:
                case ReturnCode.PhysicallyDeleted1:
                    result.Status |= RecordStatus.PhysicallyDeleted;
                    break;

                default:
                    result.Decode (response);
                    break;
            }

            if (parameters.Version != 0)
            {
                UnlockRecords (new[] { parameters.Mfn });
            }
        }
        catch (Exception exception)
        {
            throw new IrbisException
                (
                    nameof (ReadRecord) + " " + parameters,
                    exception
                );
        }

        return result;
    }

    /// <inheritdoc cref="ISyncProvider.ReadRecord{T}"/>
    /// <remarks>
    /// Повтор метода, чтобы появилась возможность делать
    /// NativeAOT в .NET 7.
    /// </remarks>
    public Record? ReadRecord
        (
            ReadRecordParameters parameters
        )
    {
        Record? result;

        try
        {
            var database = EnsureDatabase (parameters.Database);
            using var query = new SyncQuery (this, CommandCode.ReadRecord);
            query.AddAnsi (database);
            query.Add (parameters.Mfn);
            if (parameters.Version != 0)
            {
                query.Add (parameters.Version);
            }
            else
            {
                query.Add (parameters.Lock);
            }

            query.AddFormat (parameters.Format);

            using var response = ExecuteSync (query);
            if (!response.IsGood (false, ConnectionUtility.GoodCodesForReadRecord))
            {
                return null;
            }

            // TODO: забирать результат расформатирования

            result = new Record
            {
                Database = Database
            };

            switch ((ReturnCode) response.ReturnCode)
            {
                case ReturnCode.PreviousVersionNotExist:
                    result.Status |= RecordStatus.Absent;
                    break;

                case ReturnCode.PhysicallyDeleted:
                case ReturnCode.PhysicallyDeleted1:
                    result.Status |= RecordStatus.PhysicallyDeleted;
                    break;

                default:
                    result.Decode (response);
                    break;
            }

            if (parameters.Version != 0)
            {
                UnlockRecords (new[] { parameters.Mfn });
            }
        }
        catch (Exception exception)
        {
            throw new IrbisException
                (
                    nameof (ReadRecord) + " " + parameters,
                    exception
                );
        }

        return result;
    }

    /// <inheritdoc cref="ISyncProvider.ReadRecord{T}"/>
    /// <remarks>
    /// Повтор метода, чтобы появилась возможность делать
    /// NativeAOT в .NET 7.
    /// </remarks>
    public RawRecord? ReadRawRecord
        (
            ReadRecordParameters parameters
        )
    {
        RawRecord? result;

        try
        {
            var database = EnsureDatabase (parameters.Database);
            using var query = new SyncQuery (this, CommandCode.ReadRecord);
            query.AddAnsi (database);
            query.Add (parameters.Mfn);
            if (parameters.Version != 0)
            {
                query.Add (parameters.Version);
            }
            else
            {
                query.Add (parameters.Lock);
            }

            query.AddFormat (parameters.Format);

            using var response = ExecuteSync (query);
            if (!response.IsGood (false, ConnectionUtility.GoodCodesForReadRecord))
            {
                return null;
            }

            // TODO: забирать результат расформатирования

            result = new RawRecord
            {
                Database = Database
            };

            switch ((ReturnCode) response.ReturnCode)
            {
                case ReturnCode.PreviousVersionNotExist:
                    result.Status |= RecordStatus.Absent;
                    break;

                case ReturnCode.PhysicallyDeleted:
                case ReturnCode.PhysicallyDeleted1:
                    result.Status |= RecordStatus.PhysicallyDeleted;
                    break;

                default:
                    result.Decode (response);
                    break;
            }

            if (parameters.Version != 0)
            {
                UnlockRecords (new[] { parameters.Mfn });
            }
        }
        catch (Exception exception)
        {
            throw new IrbisException
                (
                    nameof (ReadRecord) + " " + parameters,
                    exception
                );
        }

        return result;
    }

    /// <inheritdoc cref="ISyncProvider.ReadRecordPostings"/>
    public TermPosting[]? ReadRecordPostings
        (
            ReadRecordParameters parameters,
            string prefix
        )
    {
        if (!CheckProviderState() || string.IsNullOrEmpty (prefix))
        {
            return null;
        }

        using var query = new SyncQuery (this, CommandCode.GetRecordPostings);
        query.AddAnsi (EnsureDatabase (parameters.Database));
        query.Add (parameters.Mfn);
        query.AddUtf (prefix);

        return ExecuteSync (query).Transform (TermPosting.Parse);
    }

    /// <inheritdoc cref="ISyncProvider.ReadTerms"/>
    public Term[]? ReadTerms
        (
            TermParameters parameters
        )
    {
        if (!CheckProviderState())
        {
            return null;
        }

        var command = parameters.ReverseOrder
            ? CommandCode.ReadTermsReverse
            : CommandCode.ReadTerms;
        using var query = new SyncQuery (this, command);
        parameters.Encode (this, query);
        using var response = ExecuteSync (query);

        return !response.IsGood (false, ConnectionUtility.GoodCodesForReadTerms) ? null : Term.Parse (response);
    }

    /// <inheritdoc cref="IAsyncProvider.ReadTextFileAsync"/>
    public string? ReadTextFile
        (
            FileSpecification specification
        )
    {
        Sure.VerifyNotNull (specification);

        return ExecuteSync
                (
                    CommandCode.ReadDocument,
                    specification.ToString()
                )
            .TransformNoCheck
                (
                    resp => IrbisText.IrbisToWindows (resp.ReadRemainingAnsiText())
                );
    }

    /// <inheritdoc cref="ISyncProvider.ReloadDictionary"/>
    public bool ReloadDictionary (string? databaseName = default) =>
        ExecuteSync (CommandCode.ReloadDictionary, EnsureDatabase (databaseName)).IsGood();

    /// <inheritdoc cref="ISyncProvider.ReloadMasterFile"/>
    public bool ReloadMasterFile (string? databaseName = default) =>
        ExecuteSync (CommandCode.ReloadMasterFile,
            databaseName ?? Database.ThrowIfNull (nameof (Database))).IsGood();

    /// <inheritdoc cref="ISyncProvider.RestartServer"/>
    public bool RestartServer() => ExecuteSync (CommandCode.RestartServer).IsGood();

    /// <inheritdoc cref="ISyncProvider.Search"/>
    public FoundItem[]? Search
        (
            SearchParameters parameters
        )
    {
        if (!CheckProviderState())
        {
            return null;
        }

        using var query = new SyncQuery (this, CommandCode.Search);
        parameters.Encode (this, query);

        return ExecuteSync (query).Transform (FoundItem.Parse);
    }

    /// <inheritdoc cref="ISyncProvider.TruncateDatabase"/>
    public bool TruncateDatabase (string? databaseName = default) =>
        ExecuteSync (CommandCode.EmptyDatabase, EnsureDatabase (databaseName)).IsGood();

    /// <inheritdoc cref="ISyncProvider.UnlockDatabase"/>
    public bool UnlockDatabase
        (
            string? databaseName = default
        )
    {
        return ExecuteSync (CommandCode.UnlockDatabase, EnsureDatabase (databaseName)).IsGood();
    }

    /// <inheritdoc cref="ISyncProvider.UnlockRecords"/>
    public bool UnlockRecords
        (
            IEnumerable<int> mfnList,
            string? databaseName = default
        )
    {
        using var query = new SyncQuery (this, CommandCode.UnlockRecords);
        query.AddAnsi (EnsureDatabase (databaseName));
        var counter = 0;
        foreach (var mfn in mfnList)
        {
            query.Add (mfn);
            ++counter;
        }

        // Если список MFN пуст, считаем операцию успешной
        if (counter == 0)
        {
            return true;
        }

        return ExecuteSync (query).IsGood();
    }

    /// <inheritdoc cref="ISyncProvider.UpdateIniFile"/>
    public bool UpdateIniFile
        (
            IEnumerable<string> lines
        )
    {
        Sure.NotNull ((object) lines);

        using var query = new SyncQuery (this, CommandCode.UpdateIniFile);
        var counter = 0;
        foreach (var line in lines)
        {
            if (!string.IsNullOrWhiteSpace (line))
            {
                query.AddAnsi (line);
                ++counter;
            }
        }

        // Если список обновляемых строк пуст, считаем операцию успешной
        if (counter == 0)
        {
            return true;
        }

        return ExecuteSync (query).IsGood();
    }

    /// <inheritdoc cref="ISyncProvider.UpdateUserList"/>
    public bool UpdateUserList
        (
            IEnumerable<UserInfo> users
        )
    {
        Sure.NotNull ((object) users);

        if (!CheckProviderState())
        {
            return false;
        }

        using var query = new SyncQuery (this, CommandCode.SetUserList);
        var counter = 0;
        foreach (var user in users)
        {
            query.AddAnsi (user.Encode());
            ++counter;
        }

        // Если список обновляемых пользователей пуст, считаем операцию неуспешной
        if (counter == 0)
        {
            return false;
        }

        return ExecuteSync (query).IsGood();
    }

    /// <inheritdoc cref="ISyncProvider.WriteRecord"/>
    public bool WriteRecord
        (
            WriteRecordParameters parameters
        )
    {
        Sure.NotNull (parameters);

        var record = parameters.Record;
        if (record is not null)
        {
            var database = EnsureDatabase (record.Database);
            using var query = new SyncQuery (this, CommandCode.UpdateRecord);
            query.AddAnsi (database);
            query.Add (parameters.Lock);
            query.Add (parameters.Actualize);
            query.AddUtf (record.Encode());

            using var response = ExecuteSync (query);
            if (!response.IsGood())
            {
                return false;
            }

            var result = response.ReturnCode;
            if (!parameters.DontParse)
            {
                record.Database ??= database;
                record.Decode (response);
            }

            parameters.MaxMfn = result;

            return true;
        }

        var records = parameters.Records.ThrowIfNull (nameof (parameters.Records));
        if (records.Length == 0)
        {
            return true;
        }

        if (records.Length == 1)
        {
            parameters.Record = records[0];
            parameters.Records = null;
            var result2 = WriteRecord (parameters);
            parameters.Record = null;
            parameters.Records = records;

            return result2;
        }

        return this.WriteRecords
            (
                records,
                parameters.Lock,
                parameters.Actualize,
                parameters.DontParse
            );
    }

    /// <inheritdoc cref="ISyncProvider.WriteTextFile"/>
    public bool WriteTextFile
        (
            FileSpecification specification
        )
    {
        Sure.VerifyNotNull (specification);

        return ExecuteSync (CommandCode.ReadDocument, specification.ToString()).IsGood();
    }

    #endregion

    #region IAsyncDisposable members

    /// <inheritdoc cref="IAsyncDisposable.DisposeAsync"/>
    public override ValueTask DisposeAsync()
    {
        Dispose();

        return ValueTask.CompletedTask;
    }

    #endregion

    #region IDisposable members

    /// <inheritdoc cref="IDisposable.Dispose"/>
    public override void Dispose()
    {
        Disconnect();
    }

    #endregion
}
